#include "virtual_device.h"

#define DEV_MAJOR 196
#define MINORBITS 20
#define DEV_NAME "virtual-device"
#define MINOR_ALLOCED ((void *)-1)

static DEFINE_IDR(device_minor_idr);

static int alloc_minor(int *minor)
{
	int ret;

	idr_preload(GFP_KERNEL);
	ret = idr_alloc(&device_minor_idr, MINOR_ALLOCED, 0, 1 << MINORBITS, GFP_NOWAIT);
	idr_preload_end();
	if (ret < 0) {
		return ret;
	}
	*minor = ret;
	return 0;
}

static int create_gendisk(virtual_device_t *device)
{
	int ret = -1;
	int minor = -1;
	struct gendisk *disk = NULL;

	ret = register_blkdev(DEV_MAJOR, DEV_NAME);
	if (ret < 0) {
		return -1;
	}

	ret = alloc_minor(&minor);
	if (ret < 0) {
		return -1;
	}

	disk = blk_alloc_disk(NUMA_NO_NODE);
	if (disk == NULL) {
		idr_remove(&device_minor_idr, minor);
		return -1;
	}
	disk = device->disk;
	disk->major = DEV_MAJOR;
	disk->minors = 1;
	disk->first_minor = minor;
	disk->flags |= GENHD_FL_NO_PART;
	disk->fops = NULL;
	disk->private_data = device;
	sprintf(disk->disk_name, "%s-%d", DEV_NAME, minor);
	device->disk = disk;
	device->dev_queue = disk->queue;
	return 0;
}

static void delete_gendisk(virtual_device_t *device)
{
	del_gendisk(device->disk);
	idr_remove(&device_minor_idr, device->minor);
	unregister_blkdev(DEV_MAJOR, DEV_NAME);
}

virtual_device_t *create_virtual_device(struct block_device *dev_block)
{
	int ret = -1;
	virtual_device_t *device = NULL;

	device = kmalloc(sizeof(virtual_device_t), GFP_KERNEL);
	if (device == NULL) {
		return NULL;
	}
	device->dev_block = dev_block;

	ret = create_gendisk(device);
	if (ret == -1) {
		kfree(device);
		return NULL;
	}
	ret = init_device_queue(device);
	if (ret == -1) {
		delete_gendisk(device);
		kfree(device);
		return NULL;
	}
	ret = add_disk(device->disk);
	if (ret) {
		exit_device_queue(device);
		delete_gendisk(device);
		kfree(device);
		return NULL;
	}
	return device;
}

int delete_virtual_device(virtual_device_t *device)
{
	exit_device_queue(device);
	delete_gendisk(device);
	kfree(device);
	return 0;
}
